/*
SteelNzb
Copyright (C) 2008  Wessel v. Staal

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

/* Wessel van Staal */
/* NZB file reader */

#include "nzb.h"
#include "global.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#include <libxml/xmlreader.h>

#if defined(WIN32) || defined(WINCE)
#include <windows.h>
#define stricmp _stricmp
#define unlink(x) DeleteFileA(x)
#ifndef WINCE
#include <direct.h>
#include <crtdbg.h>
#endif
#else
#include <unistd.h>
#define stricmp strcasecmp
#endif

typedef struct nzbFileState
{
	xmlTextReaderPtr currentFilePtr;
	char currentNzbFilename[512];
	int internalNum;
	int numPartsRead;
} nzbFileState;

int nzb_moveToNode(xmlTextReaderPtr rdr, char* name);
int nzb_moveNextNode(xmlTextReaderPtr rdr);

/**
	Open nzb file and returns state
	@param file Path to the file
	@returns pointer to state, or null indicating failure
*/
void* nzb_openNzb(char* file)
{
	xmlTextReaderPtr rdr = xmlReaderForFile(file, NULL, 0);
	nzbFileState* state= NULL;

	if ( rdr == NULL )
		return NULL;

	if ( (state = malloc(sizeof(nzbFileState))) == NULL )
	{	
		perror("nzb_openNzb");
		exit(1);
	}
	
	memset(state, 0, sizeof(nzbFileState));
	state->currentFilePtr = rdr;
	strcpy(state->currentNzbFilename, file);

	return state;
}

/**
	Returns number of segments in the nzb file in the passed state
	File must not be exclusively locked
	The passed state is not affected
	@param state Pointer to an open nzb state
	@returns number of segments
*/
int nzb_getArticleCountAndValidate(void* stateptr, char* xsdpath)
{
	int ret = 0;
	nzbFileState* state = stateptr;
	xmlTextReaderPtr rdr = xmlReaderForFile(state->currentNzbFilename, NULL, 0);
	const xmlChar* tagname;

	if ( xmlTextReaderSchemaValidate(rdr, xsdpath) != 0 )
	{
		xmlFreeTextReader(rdr);
		fprintf(stderr, "NzbValidate::Could not load XSD schema 'nzb.xsd'");
		return -1;
	}

	while ( nzb_moveNextNode(rdr) )
	{
		tagname = xmlTextReaderConstName(rdr);
		
		/* just count all segments */
		if ( tagname != NULL && strcmp((char*)tagname, "segment") == 0 )
			ret++;
	}

	if ( xmlTextReaderIsValid(rdr) != 1 )
		ret = -1;

	xmlFreeTextReader(rdr);

	return ret;
}

/**
	Returns file information from the nzb file in the passed state
	@param stateptr The nzb state
	@returns pointer to file structure, or NULL indicating EOF or error
*/
nzbFile* nzb_getNextFile(void* stateptr)
{
	const xmlChar* tagname = NULL;
	const xmlChar* file_value = NULL;
	nzbFileState* state = stateptr;
	char* quote1 = NULL, *quote2 = NULL;
	nzbFile* file = NULL;

	if ( state == NULL )
		return NULL;

	tagname = xmlTextReaderConstName(state->currentFilePtr);

	/* reset the number of parts read, recount for the new file */
	state->numPartsRead = 0;

	if ( !nzb_moveToNode(state->currentFilePtr, "file") )
	{
		state->internalNum++;
		return NULL;
	}

	if ( (file_value = xmlTextReaderGetAttribute(state->currentFilePtr, (unsigned char*) "subject")) == NULL )
	{
		fprintf(stderr, "NzbGetNextFile::Missing attribute 'subject'!\n");
		return NULL;
	}
	
	/* build file structure we will return */
	if ( (file = malloc(sizeof(nzbFile))) == NULL )
	{
		perror("Nzb::GetNextFile");
		exit(1);
	}

	memset(file, 0, sizeof(nzbFile));

	/* if there are quotes in the subject attribute, use the content as filename */
	if ( (quote1 = strchr((char*) file_value, '"')) != NULL )
	{
		if ( (quote2 = strchr((char*) quote1+1, '"')) != NULL )
		{
			memcpy(file->filename, quote1+1, quote2-quote1-1);
		}
	}
	else
		strcpy(file->filename, (char*)file_value);

	if ( !nzb_moveToNode(state->currentFilePtr, "group") )
	{
		fprintf(stderr, "NzbGetNextFile::Missing element 'group'!\n");
		free(file);
		return NULL;
	}

	xmlTextReaderRead(state->currentFilePtr);

	strcpy(file->group, (char*) xmlTextReaderConstValue(state->currentFilePtr));
	state->internalNum++;

	file->internalNum = state->internalNum;

	if ( !nzb_moveToNode(state->currentFilePtr, "segments") )
	{
		fprintf(stderr, "NzbGetNextFile::Missing element 'segments'!\n");
		free(file);
		return NULL;
	}
	else
	{
		nzb_moveToNode(state->currentFilePtr, "segment");
	}

	return file;
}

/**
	Moves current file pointer in state to a particular start node
	Moves recursively all the way down until positive match
	@param rdr The xml file pointer
	@param name The name of the xml tag to move to
	@returns int indicating success or EOF
*/
int nzb_moveToNode(xmlTextReaderPtr rdr, char* name)
{
	const xmlChar* tagname = NULL;
	int parse_ret = 0;

	tagname = xmlTextReaderConstName(rdr);

	while ( tagname == NULL || xmlTextReaderNodeType(rdr) == XML_READER_TYPE_END_ELEMENT || strcmp((char*) tagname, name) != 0 )
	{
		if ( (parse_ret = xmlTextReaderRead(rdr)) != 1 )
			return 0;

		tagname = xmlTextReaderConstName(rdr);
	}

	return 1;
}

/**
	Moves xml file pointer to next begin node 
	@param rdr The xml file pointer
	@returns int indicating success or EOF
*/
int nzb_moveNextNode(xmlTextReaderPtr rdr)
{
	int type = 0;

	do
	{
		if ( xmlTextReaderRead(rdr) != 1 )
			return 0;

		type = xmlTextReaderNodeType(rdr);
	}
	while ( type == XML_READER_TYPE_TEXT ||
		type == XML_READER_TYPE_END_ELEMENT || type == XML_READER_TYPE_SIGNIFICANT_WHITESPACE );

	return 1;
}

/**
	Reads segment information from nzb state
	@param stateptr Pointer to nzb state
	@returns Pointer to nzbFilePart structure or NULL indicating error or end of segments in file
*/
nzbFilePart* nzb_getNextPart(void* stateptr)
{
	const xmlChar* tagname = NULL;
	const xmlChar* segment_num = NULL;
	const xmlChar* msg_id = NULL;
	const xmlChar* bytes = NULL;
	nzbFilePart* file_part = NULL;
	nzbFileState* state = stateptr;

	tagname = xmlTextReaderConstName(state->currentFilePtr);

	if ( tagname == NULL || strcmp((char*) tagname, "segment") != 0 )
		return NULL;

	state->numPartsRead++;

	bytes = xmlTextReaderGetAttribute(state->currentFilePtr, (unsigned char*) "bytes");
	segment_num = xmlTextReaderGetAttribute(state->currentFilePtr,(unsigned char*) "number");
	
	/* allocate filepart structure we will return */
	if ( (file_part = malloc(sizeof(nzbFilePart))) == NULL )
	{
		perror("NzbGetNextPart");
		exit(1);
	}

	memset(file_part, 0, sizeof(nzbFilePart));

	file_part->segment = atoi((char*) segment_num);
	file_part->size = atoi((char*) bytes);

	if ( xmlTextReaderRead(state->currentFilePtr) != 1 )
	{
		free(file_part);
		return NULL;
	}
	msg_id = xmlTextReaderConstValue(state->currentFilePtr);

	strcpy(file_part->id, (char*) msg_id);

	if ( xmlTextReaderRead(state->currentFilePtr) != 1 )
	{
		free(file_part);
		return NULL;
	}
	nzb_moveNextNode(state->currentFilePtr);

	return file_part;
}

/**
	Returns whether the passed state is EOF
	@param stateptr Pointer to nzb state
	@returns int indicating EOF or not (0, 1)
*/
int nzb_isEof(void* stateptr)
{
	nzbFileState* state = stateptr;

	return xmlTextReaderReadState(state->currentFilePtr) == XML_TEXTREADER_MODE_EOF;
}

/**
	Returns path from nzb state
	@param state The nzb state
	@returns path to nzb file
*/
char* nzb_getCurrentNzbFilename(void* state)
{
	nzbFileState * filestate = state;
	return filestate->currentNzbFilename;
}

/**
	Closes nzb state and frees resources
	@param stateptr Pointer to nzb state
	@returns void
*/
void nzb_closeNzb(void* stateptr)
{
	nzbFileState* state = stateptr;
	xmlFreeTextReader(state->currentFilePtr);
	free(state);
}

/**
	Returns number of segments read for the current file
	@param cfg Pointer to the config data
	@returns pointer to manager state
*/
int nzb_getNumPartsRead(void* state)
{
	nzbFileState* filestate = state;
	return filestate->numPartsRead;
}

/**
	Returns the current unique identifier for the current file
	@param state Pointer to nzb state
	@returns current identifier
*/
int nzb_getCurrentInternalNum(void* state)
{
	nzbFileState* filestate = state;
	return filestate->internalNum;
}
